/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.nanosn.coffeemaker.databaseproject.persistence;

import japa.parser.ast.CompilationUnit;
import japa.parser.ast.ImportDeclaration;
import japa.parser.ast.body.BodyDeclaration;
import japa.parser.ast.body.FieldDeclaration;
import japa.parser.ast.body.MethodDeclaration;
import japa.parser.ast.body.ModifierSet;
import japa.parser.ast.body.Parameter;
import japa.parser.ast.body.TypeDeclaration;
import japa.parser.ast.body.VariableDeclarator;
import japa.parser.ast.body.VariableDeclaratorId;
import japa.parser.ast.expr.AnnotationExpr;
import japa.parser.ast.expr.AssignExpr;
import japa.parser.ast.expr.MarkerAnnotationExpr;
import japa.parser.ast.expr.NameExpr;
import japa.parser.ast.expr.ThisExpr;
import japa.parser.ast.stmt.BlockStmt;
import japa.parser.ast.stmt.ExpressionStmt;
import japa.parser.ast.stmt.ReturnStmt;
import japa.parser.ast.stmt.Statement;
import japa.parser.ast.type.PrimitiveType;
import japa.parser.ast.type.VoidType;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.lang.StringUtils;

/**
 *
 * @author selkhateeb
 */
public class EntityModelEnhancer {

    public synchronized static void Enhance(CompilationUnit cu) {
        //1. add annotations
        AddNecessaryAnnotations(cu);
        //2. Add getters and setters
        AddGettersAndSetters(cu);

        //3. fix imports
        FixImports(cu);
        //4. fix package name if needed
    }

    public synchronized static void AddNecessaryAnnotations(CompilationUnit cu) {
        AddClassAnnotations(cu.getTypes().get(0));
        AddIdFieldAnnotations(cu.getTypes().get(0).getMembers());
        AddBasicFieldAnnotations(cu.getTypes().get(0).getMembers());

        AddRelationFieldAnnotations(cu.getTypes().get(0).getMembers());

    }

    public synchronized static void AddGettersAndSetters(CompilationUnit cu) {

        
        TypeDeclaration clazz = cu.getTypes().get(0);

        List<BodyDeclaration> bodyDeclarations = clazz.getMembers();

        List<MethodDeclaration> newMethodDeclarations = new ArrayList<MethodDeclaration>();

        List<MethodDeclaration> existingMethodDeclarations = new ArrayList<MethodDeclaration>();

        for (BodyDeclaration bodyDeclaration : bodyDeclarations) {
            if (bodyDeclaration instanceof MethodDeclaration) {
                existingMethodDeclarations.add((MethodDeclaration) bodyDeclaration);
            }
            if (bodyDeclaration instanceof FieldDeclaration) {
                FieldDeclaration field = (FieldDeclaration) bodyDeclaration;
                field.setModifiers(ModifierSet.PRIVATE);

                newMethodDeclarations.add(CreateGetter(field));
                newMethodDeclarations.add(CreateSetter(field));
            }
        }

        if (existingMethodDeclarations.isEmpty()) {
            bodyDeclarations.addAll(newMethodDeclarations);
        }
        for (MethodDeclaration methodDeclaration : existingMethodDeclarations) {
            for (MethodDeclaration newMethodDeclaration : newMethodDeclarations) {
                if (!methodDeclaration.getName().equals(newMethodDeclaration.getName())) {
                    bodyDeclarations.add(newMethodDeclaration);
                }

            }
        }
        


    }

    private synchronized static MethodDeclaration CreateGetter(FieldDeclaration field) {

        MethodDeclaration getter = new MethodDeclaration(ModifierSet.PUBLIC, field.getType(), "get" + StringUtils.capitalize(field.getVariables().get(0).getId().getName()));
        BlockStmt block = new BlockStmt();
        ReturnStmt ret = new ReturnStmt(new ThisExpr(new NameExpr(field.getVariables().get(0).getId().getName())));
        List<Statement> statements = new ArrayList<Statement>();
        statements.add(ret);
        block.setStmts(statements);
        getter.setBody(block);

        return getter;
    }

    private synchronized static MethodDeclaration CreateSetter(FieldDeclaration field) {

        Parameter p = new Parameter(field.getType(), field.getVariables().get(0).getId());
        MethodDeclaration setter =
                new MethodDeclaration(
                ModifierSet.PUBLIC, new VoidType(),
                "set" + StringUtils.capitalize(field.getVariables().get(0).getId().getName()),
                p);
        BlockStmt block = new BlockStmt();
        List<Statement> statements = new ArrayList<Statement>();
        statements.add(new ExpressionStmt(new AssignExpr(new ThisExpr(new NameExpr(field.getVariables().get(0).getId().getName())), new NameExpr(field.getVariables().get(0).getId().getName()), AssignExpr.Operator.assign)));
        block.setStmts(statements);
        setter.setBody(block);
        return setter;
    }

    private synchronized static void AddClassAnnotations(TypeDeclaration clazz) {

        AnnotationExpr annotationExprEntity = new MarkerAnnotationExpr(new NameExpr("Entity"));

        //Check if has the @Entity annotation
        List<AnnotationExpr> annotationExprs = clazz.getAnnotations();
        if (annotationExprs != null) {
            for (AnnotationExpr annotationExpr : annotationExprs) {
                if (annotationExpr.equals(annotationExprEntity)) {
                    //Our work is done here .. The class is already
                    // annotated with what we want
                    return;
                }
            }
        }
        if (annotationExprs == null) {
            annotationExprs = new ArrayList<AnnotationExpr>();
        }
        annotationExprs.add(annotationExprEntity);
        clazz.setAnnotations(annotationExprs);
    }

    private synchronized static void AddIdFieldAnnotations(List<BodyDeclaration> members) {

        AnnotationExpr annotationExprId = new MarkerAnnotationExpr(new NameExpr("Id"));

        for (BodyDeclaration bodyDeclaration : members) {
            if (!(bodyDeclaration instanceof FieldDeclaration)) {
                continue;
            }
            FieldDeclaration field = (FieldDeclaration) bodyDeclaration;

            //Is it annotated with Id
            List<AnnotationExpr> annotationExprs = field.getAnnotations();
            if (annotationExprs != null) {
                for (AnnotationExpr annotationExpr : annotationExprs) {
                    if (annotationExpr.equals(annotationExprId)) {
                        return;
                    }
                }
            }

            //get the id field and add the annotation to it
            if (field.getVariables().get(0).getId().getName().equalsIgnoreCase("id")) {
                field.getAnnotations().add(annotationExprId);
                return;
            }
        }

        FieldDeclaration idField = new FieldDeclaration(ModifierSet.PRIVATE,
                                                        new PrimitiveType(PrimitiveType.Primitive.Long), new VariableDeclarator(new VariableDeclaratorId("id")));

        List<AnnotationExpr> la = idField.getAnnotations();
        if (la == null) {
            la = new ArrayList<AnnotationExpr>();
        }
        la.add(annotationExprId);
        idField.setAnnotations(la);
        members.add(idField);
    }

    private synchronized static void AddBasicFieldAnnotations(List<BodyDeclaration> members) {
        AnnotationExpr annotationExprBasic = new MarkerAnnotationExpr(new NameExpr("Basic"));

        for (BodyDeclaration bodyDeclaration : members) {
            if (!(bodyDeclaration instanceof FieldDeclaration)) {
                continue;
            }
            FieldDeclaration field = (FieldDeclaration) bodyDeclaration;

            List<AnnotationExpr> la = field.getAnnotations();
            if (la == null) {
                la = new ArrayList<AnnotationExpr>();
            }
            la.add(annotationExprBasic);
            field.setAnnotations(la);

        }
    }

    private synchronized static void AddRelationFieldAnnotations(List<BodyDeclaration> members) {
        //TODO
    }

    private synchronized static void FixImports(CompilationUnit cu) {

        //TODO needs a lot more work than this
        List<ImportDeclaration> imports = cu.getImports();
        if (imports == null) {
            imports = new ArrayList<ImportDeclaration>();
        }
        ImportDeclaration java_sql_ = new ImportDeclaration(new NameExpr("java.sql"), false, true);
        ImportDeclaration javax_persistence_ = new ImportDeclaration(new NameExpr("javax.persistence"), false, true);

        imports.add(java_sql_);
        imports.add(javax_persistence_);
        cu.setImports(imports);
    }
}
